package eva

import (
	"testing"

	"gitcode.com/deyiyangyang/gascheme/lex"
	"gitcode.com/deyiyangyang/gascheme/obj"
	"gitcode.com/deyiyangyang/gascheme/par"
)

// TestLetBasic tests basic let expressions
func TestLetBasic(t *testing.T) {
	// Create a let expression: (let ((x 2) (y 3)) (+ x y))
	input := "(let ((x 2) (y 3)) (+ x y))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 5
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 5 {
		t.Errorf("Eval returned wrong value. Got %d, want 5", integer.Value)
	}
}

// TestLetWithComplexBody tests let expressions with complex body expressions
func TestLetWithComplexBody(t *testing.T) {
	// Create a let expression: (let ((x 2) (y 3)) (* (+ x y) 2))
	input := "(let ((x 2) (y 3)) (* (+ x y) 2))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 10
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 10 {
		t.Errorf("Eval returned wrong value. Got %d, want 10", integer.Value)
	}
}

// TestNestedLet tests nested let expressions
func TestNestedLet(t *testing.T) {
	// Create a nested let expression: (let ((x 2)) (let ((y 3)) (+ x y)))
	input := "(let ((x 2)) (let ((y 3)) (+ x y)))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 5
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 5 {
		t.Errorf("Eval returned wrong value. Got %d, want 5", integer.Value)
	}
}

// TestLetWithNoBindings tests let expressions with no bindings
func TestLetWithNoBindings(t *testing.T) {
	// Create a let expression: (let () (+ 2 3))
	input := "(let () (+ 2 3))"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 5
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 5 {
		t.Errorf("Eval returned wrong value. Got %d, want 5", integer.Value)
	}
}

// TestLetWithSingleBinding tests let expressions with a single binding
func TestLetWithSingleBinding(t *testing.T) {
	// Create a let expression: (let ((x 42)) x)
	input := "(let ((x 42)) x)"

	// Parse the expression
	l := lex.NewLexer(input)
	p := par.NewParser(l)
	expr, err := p.ParseExpression()
	if err != nil {
		t.Fatalf("Parse error: %s", err)
	}

	// Create environment and register built-in procedures
	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	// Evaluate the expression
	result := Eval(expr, env)

	// Check if the result is an Integer object with value 42
	integer, ok := result.(*obj.Integer)
	if !ok {
		t.Fatalf("Eval didn't return Integer. Got %T", result)
	}

	if integer.Value != 42 {
		t.Errorf("Eval returned wrong value. Got %d, want 42", integer.Value)
	}
}